/*
    This file is part of Lua Invoke.
    Copyright 2005-2007 Rafael de Oliveira Jannone <rafael@jannone.org>

    Lua Invoke is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    Foobar is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/* callbacks implementation for invoke */

#include <malloc.h>

#include "callback.h"
#include "cbslots.h"
#include "type.h"
#include "marshall.h"

/* callback  functions */

callback_t*
callback_nextfree() 
{
	int c;
	for (c = 0; c < MAX_SLOTS; c++) {
		if (cb_slots[c].sign == NULL) {
			return &cb_slots[c];
		}
	}
	return NULL;
}

void 
callback_unregister(callback_t* cb) 
{
	//printf("invoke module: destroying callback_t* at %x\n", cb);
	
	if (cb->sign != NULL) {
		free((void*)cb->sign);
	}
	cb->sign = NULL;
}

void
callback_init_slots() {
	int c;
	for (c=0; c<MAX_SLOTS; c++) {
		cb_slots[c].slot = c;
		cb_slots[c].callback = code_slots[c];
		cb_slots[c].sign = NULL;
		/*printf("invoke module: init slot at %x (%i) with %x\n", &cb_slots[c], c, cb_slots[c].callback);*/
	}	
}

LL 
callback_run(callback_t* cb, long x, va_list ap)
{
	lua_State *L;

	L = cb->L;
	lua_pushstring(L, MYNAME);
	lua_gettable(L, LUA_GLOBALSINDEX);
	lua_pushstring(L, "_cb_slots");
	lua_rawget(L, -2);
	lua_remove(L, -2);
	lua_rawgeti (L, -1, cb->slot);	/* grab lua function */
	lua_remove(L, -2);

	/* FIXME: ugly hack, not portable, as all the rest... */
	/* rewind our va_list so we can recover the first arg */
	*(int**)&ap = (int*)(ap) - 1;
	
	/* FIXME: demarshall C->lua on lua stack */
	const char* sign = cb->sign;
	
	/* read return type token */
	type_t rt;
	sign = type_read(&rt, sign);
	
	/* demarshall */
	type_t t;
	int npar = 0, ns;
	LL arg;
	while (*sign) {
		sign = type_read(&t, sign);
		/* convert vararg to LL */
		switch (t.type) {
			case 'c':	/* char */
			case 'z':	/* short */
				/* those are promoted to int on '...' */
				{
					int v = va_arg(ap, int);
					arg = *(long*)&v;
				}
				break;
			case 'p': /* pointer */
				{
					void* v = va_arg(ap, void*);
					arg = (long)v;
				}
				break;
			case 'i':	/* integer */
				if (t.unsign)
				{
					unsigned int v = va_arg(ap, unsigned int);
					arg = *(long*)&v;
				} else {
					int v = va_arg(ap, int);
					arg = *(long*)&v;
				}
				break;			
			case 'l':	/* long */
				if (t.unsign)
				{
					unsigned long v = va_arg(ap, unsigned long);
					arg = *(long*)&v;
				} else {
					arg = va_arg(ap, long);
				}
				break;			
			case 's':	/* string (const char*) */
				{
					const char* v = va_arg(ap, const char*);
					arg = *(long*)&v;
				}
				break;
			case 'L':	/* long long */
				if (t.unsign)
				{
					unsigned long long v = va_arg(ap, unsigned long long);
					arg = *(LL*)&v;
				} else {
					long long v = va_arg(ap, long long);
					arg = *(LL*)&v;
				}
				break;
			case 'd':	/* double */
			case 'f':	/* float */
				/* float is promoted to double on '...', don't ask me why :p */
				{
					double v = va_arg(ap, double);
					arg = *(LL*)&v;
				}
				break;
		}
		/* push on lua stack */
		ns = demarshall(L, arg, &t);
		/*
		if (ns == 0) {
			lua_pushstring(L, "can't marshall callback parameter to lua");
			lua_error(L);			
		}
		*/
		npar += ns;
	}
	
	va_end(ap);
	
	int nret = (rt.type == 'v') ? 0 : 1;
	lua_call(L, npar, nret);
	
	LL ret = 0;
	if (nret) {
		marshall(L, -1, (long*)&ret, &rt);
	}
	
	return ret;
}
